﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using XLETL.BLL;
using XLETL.Model;
using XLETL.Common;
using XLETL.ETLEngine;
using System.Runtime.Remoting.Messaging;

namespace XLETL.Scheduler
{
  // Define delegate for asyncronous ETL processing
  public delegate string ETLProcessed(object taskID);


  public partial class MainForm : Form
  {
    // Business Classes Declarations 
    Schedule schedule = null;
    ETLEngine.ETLEngine etlEng = null;
    Task task = null;
    DB db = null;
    TaskInfo nextTaskInfo = null;
    
    public MainForm()
    {
      InitializeComponent();

      // Instantiate business classes
      task = new Task(); 
      db = new DB();

      BuildTaskSchedule();      

      timer1.Interval = 500;
      timer1.Enabled = true;     

    }

    /// <summary>
    /// Method to build the Task Schedule Time list
    /// </summary>
    private void BuildTaskSchedule()
    {
      try
      {
        // Instantiate schedule business object
        if (schedule == null)
          schedule = new Schedule();

        // Retrieve the whole schedule list from the storage 
        List<ScheduleInfo> siList = schedule.GetScheduleList();

        // Build the time list for today 
        ScheduleTask.TaskQueue = CreateScheduleTime(siList);

        // Set the initial task to run
        ScheduleTask.SetInitialTask();

        // Display Schedule details
        this.lblTotalTasks.Text = ScheduleTask.TaskQueue.Length.ToString();
        this.lblLatSyncTime.Text = DateTime.Now.ToString();

        // Display the next task's details
        DisplayETLDetails();
      }
      catch (Exception ex)
      {
        tbLog.AppendText(ex.Message);              
      }
    }

    /// <summary>
    /// Method to Display the details of the next task in the queue
    /// </summary>
    private void DisplayETLDetails()
    {
      try
      {
        if (!ScheduleTask.isEndOfTaskList)
        {
          nextTaskInfo = task.GetTaskDetails(ScheduleTask.TaskQueue[ScheduleTask.CurrentTaskID].TaskID);
          if (nextTaskInfo != null)
          {
            this.lblTaskDbName.Text = db.GetDBDetails(nextTaskInfo.DatabaseID).DBFriendlyName;

            this.lblTaskName.Text = nextTaskInfo.TaskDetails.Name;
            this.lblTaskNumInQueue.Text = Convert.ToString(ScheduleTask.CurrentTaskID + 1);
          }
        }
        else
        {
          this.lblTaskDbName.Text = "";
          this.lblTaskName.Text = "";
          this.lblTaskNumInQueue.Text = "";
          this.lblTotalTasks.Text = "0";
        }
      }
      catch (Exception ex)
      {
        this.tbLog.AppendText("Error loading ETL task details\r\n");
        this.tbLog.AppendText(ex.Message);
      }
    }

    /// <summary>
    /// Method to build the ETL processing time list for the current day based on the Task Schedule collection
    /// </summary>
    /// <param name="siList">List of ScheduleInfo objects </param>
    /// <returns>Array of TaskSchedule objects</returns>
    private TaskSchedule[] CreateScheduleTime(List<ScheduleInfo> siList)
    {
      // Hold the selected Task Schedules
      List<TaskSchedule> timeList = new List<TaskSchedule>();
      
      int day = DateTime.Now.Day;
      int month = DateTime.Now.Month;
      int year = DateTime.Now.Year;
      TaskSchedule taskSchedule;
      DayOfWeek dayOfWeek;

      // Iterate through ScheduleInfo list and build the Schedule
      foreach (ScheduleInfo schInfo in siList)
      {
        // If the current schedule is of type Weekly then iterate through its WeekDays collection
        // in order to add the Task's time to the Time list if the current weekday presented  
        if (schInfo.Weekly)
        {
          foreach (string d in schInfo.WeekDays)
          {
            // Parse the string into DayOfWeek enum type
            dayOfWeek = (DayOfWeek)Enum.Parse(typeof(DayOfWeek), d);

            if (DateTime.Now.DayOfWeek.Equals(dayOfWeek))
            {
              taskSchedule = new TaskSchedule(schInfo.TaskID, new DateTime(year, month, day, schInfo.Hour, schInfo.Min, 0));
              // Add time now
              timeList.Add(taskSchedule);
              // only one week day can be equal so exit the loop
              break;
            }
          }
        }
        else
        {
          taskSchedule = new TaskSchedule(schInfo.TaskID, new DateTime(year, month, day, schInfo.Hour, schInfo.Min, 0));
          timeList.Add(taskSchedule);
        }        
      }

      // Sort the TaskSchedule list by Time
      timeList.Sort(delegate(TaskSchedule t1, TaskSchedule t2)
      {
        return t1.RunTime.CompareTo(t2.RunTime);
      });

      return timeList.ToArray();
    }

    /// <summary>
    /// Method to start ETL engine asynchronously
    /// </summary>
    /// <param name="taskID">ID of task to process</param>
    private void StartETLEngine(Guid taskID)
    {
      try
      {
        // Show main window
        this.Show();
        this.WindowState = FormWindowState.Normal;
        label2.Visible = true;
        this.tbLog.Text = "";

        if (etlEng == null)
          etlEng = new XLETL.ETLEngine.ETLEngine();

        // Excecute ETL processing using async invokation
        ETLProcessed etlPr = new ETLProcessed(etlEng.StartETLProcess);
        IAsyncResult etlAr = etlPr.BeginInvoke(taskID, new AsyncCallback(ETLProcessComplete), null);               
        
      }      
      catch (Exception ex)
      {
        throw;
      }

    }

    /// <summary>
    /// Callback Method that will be invoked once the spawned ETL process completed 
    /// </summary>
    /// <param name="etlArg">IAsyncResult parameter</param>
    protected void ETLProcessComplete(IAsyncResult etlArg)
    {
      // TODO: check for the error

      // Now get the result.
      AsyncResult ar = (AsyncResult)etlArg;

      // Retrieve the informational object and cast it to string
      ETLProcessed etlP = (ETLProcessed)ar.AsyncDelegate;
      string etlLog = etlP.EndInvoke(etlArg);      

      // Display the log info
      this.tbLog.Text = etlLog;
      this.label2.Visible = false;

      // Display Next Task details
      this.DisplayETLDetails();
    }


    /// <summary>
    /// Method to check if necessary to start an ETL processing
    /// </summary>
    private void CheckSchedule()
    {
      if (!ScheduleTask.isEndOfTaskList && ScheduleTask.RunTask())
        {
          // Process Now
          this.StartETLEngine(ScheduleTask.TaskQueue[ScheduleTask.CurrentTaskID].TaskID);
          
          // Set the next task for processing
          ScheduleTask.SetNextTask();
        }      
    }

  /// <summary>
    /// Method to run when the timer is raised
  /// </summary>  
    private void timer1_Tick(object sender, EventArgs e)
    {
      // If it is a midnight then repopulate the task schedule list
      if (DateTime.Now.Hour == 0 && DateTime.Now.Minute == 0)
      {
        this.BuildTaskSchedule();
      }
      else
      {
        this.CheckSchedule();
      }
    }

    #region MainForm methods

    private void notifyIcon1_MouseDoubleClick(object sender, MouseEventArgs e)
    {
      this.Show();
      this.WindowState = FormWindowState.Normal;
    }

    private void Form1_Resize(object sender, EventArgs e)
    {
      notifyIcon1.BalloonTipTitle = "XLETL Scheduler";
      notifyIcon1.BalloonTipText = "Scheduler is minimized and running.";

      if (FormWindowState.Minimized == this.WindowState)
      {
        notifyIcon1.Visible = true;
        notifyIcon1.ShowBalloonTip(500);
        this.Hide();
      }
      else if (FormWindowState.Normal == this.WindowState)
      {
        notifyIcon1.Visible = false;
      }

    }

    private void btnHide_Click(object sender, EventArgs e)
    {
      this.WindowState = FormWindowState.Minimized;
    }

    private void btnExit_Click(object sender, EventArgs e)
    {
      Application.Exit();
    }

    #endregion
    
  }
}
